package com.xliu.cs.openproject.lianliankan;

import com.xliu.cs.openproject.lianliankan.view.PicturePanel;

import javax.imageio.ImageIO;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

// TODO: 将 Panel 抽出来，不跟 Model 绑在一起
public class PMModel {
    /**
     * image directory
     */
    private final File imgDir;
    /**
     * (n * m) panels
     */
    private PicturePanel[][] imgPanels;
    /**
     * indicate whether each panel has image to show or not
     */
    private int[][] block;
    /**
     * the remaining number of panel has image
     */
    private int remainNumber;
    /**
     * indicate which image the panel belongs to
     */
    private String[][] posBelong;
    /**
     * the listener listen the click event
     */
    private ArrayList<ActionListener> actionListenerList;
    /**
     * the maximum number of corner a line-line can contain
     */
    private final int corner = 2;

    public PMModel() {
        this("res/pic/");
    }

    public PMModel(String imagePath) {
        imgDir = new File(imagePath);
    }


    /**
     * initialize posBelong and block
     */
    private void calImagePosition(int rows, int cols) {
        posBelong = new String[rows][cols];
        block = new int[rows + 2][cols + 2];

        Arrays.fill(block[0], 0);
        Arrays.fill(block[rows + 1], 0);
        for (int i = 1; i <= rows; i++) {
            Arrays.fill(block[i], 1);
            block[i][0] = 0;
            block[i][cols + 1] = 0;
        }

        //System.out.println(block.length + " " + block[0].length);
        //for (int i=0; i<rows+2; i++) {
        //	for (int j=0; j<cols+2; j++) {
        //		System.out.print(block[i][j] + " ");
        //	}
        //	System.out.println();
        //}
        //random generate image positions
        int size = rows * cols / 2;
        LinkedList<Coordinate> positionList = new LinkedList<Coordinate>();
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++)
                positionList.add(new Coordinate(i, j));

        for (int i = 0; i < size; i++) {
            int pos = (int) (Math.random() * positionList.size());
            Coordinate cr = positionList.get(pos);
            posBelong[cr.getX()][cr.getY()] = String.valueOf(i);
            positionList.remove(cr);

            pos = (int) (Math.random() * positionList.size());
            cr = positionList.get(pos);
            posBelong[cr.getX()][cr.getY()] = String.valueOf(i);
            positionList.remove(cr);
        }
		/*for (int i=0; i<rows; i++) {
			for (int j = 0; j < cols; j++) {
				System.out.print(posBelong[i][j] + "  ");
			}
			System.out.println();
		}*/
    }

    /**
     * Is exist a path between p1 and p2
     */
    public String existPath(Coordinate p1, Coordinate p2) {
        p1 = new Coordinate(p1.getX() + 1, p1.getY() + 1);
        p2 = new Coordinate(p2.getX() + 1, p2.getY() + 1);
        // 0 for no, 1 for yes, -1 for tmp
        boolean exist = false;
        Queue<String> positionSet = new LinkedList<String>();
        int rows = block.length;
        int cols = block[0].length;

        //add 0 corner, 1 corner, ...
        positionSet.add(p1.toString());
        int setSize = positionSet.size();
        String result = null;
        for (int i = 0; i <= corner; i++) {
            while (setSize > 0) {
                //first element is "x y"
                String str = positionSet.poll();
                setSize -= 1;
                Coordinate head = Coordinate.toCoordinate(str.split(",", 2)[0]);
                //same row
                for (int col = head.getY() - 1; col >= 0; col--) {
                    Coordinate crd = new Coordinate(head.getX(), col);
                    if (crd.equals(p2)) {
                        exist = true;
                        result = crd + "," + str;
                        break;
                    } else if (block[head.getX()][col] == 0) {
                        positionSet.add(crd.toString() + "," + str);
                        block[head.getX()][col] = -1;
                    } else break;
                }
                if (exist) break;
                for (int col = head.getY() + 1; col < cols; col++) {
                    Coordinate crd = new Coordinate(head.getX(), col);
                    if (crd.equals(p2)) {
                        exist = true;
                        result = crd + "," + str;
                        break;
                    } else if (block[head.getX()][col] == 0) {
                        positionSet.add(crd.toString() + "," + str);
                        block[head.getX()][col] = -1;
                    } else break;
                }
                if (exist) break;
                //same col
                for (int row = head.getX() - 1; row >= 0; row--) {
                    Coordinate crd = new Coordinate(row, head.getY());
                    if (crd.equals(p2)) {
                        exist = true;
                        result = crd + "," + str;
                        break;
                    } else if (block[row][head.getY()] == 0) {
                        positionSet.add(crd.toString() + "," + str);
                        block[row][head.getY()] = -1;
                    } else break;
                }
                if (exist) break;
                for (int row = head.getX() + 1; row < rows; row++) {
                    Coordinate crd = new Coordinate(row, head.getY());
                    if (crd.equals(p2)) {
                        exist = true;
                        result = crd + "," + str;
                        break;
                    } else if (block[row][head.getY()] == 0) {
                        positionSet.add(crd.toString() + "," + str);
                        block[row][head.getY()] = -1;
                    } else break;
                }
                if (exist) break;
            }
            setSize = positionSet.size();
        }
        //back -1 to 0
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                if (block[i][j] == -1)
                    block[i][j] = 0;
            }
        }

        return result;
    }

    public void readImages(int rows, int cols, int rowSize, int colSize) {
        setRemainNumber(rows * cols); //set remainNumber
        calImagePosition(rows, cols); //set posBelong, String of int represent index

        // random rows*cols/2 images path
        int imgNumber = rows * cols / 2;
        String[] imgPaths = new String[imgNumber];
        HashMap<String, BufferedImage> imgMap = new HashMap<String, BufferedImage>();
        System.out.print(imgDir.toString());
        File[] files = imgDir.listFiles();
        ArrayList<Integer> indexSet = new ArrayList<Integer>();
        //initial index
        for (int i = 0; i < imgNumber; i++) {
            int number = (int) (Math.random() * files.length);
            indexSet.add(number);
        }
        for (int i = 0; i < imgNumber; i++) {
            int index = indexSet.get(i);
            imgPaths[i] = files[index].getPath();
        }
        for (String imgPath : imgPaths) {
            try {
                if (!imgMap.containsKey(imgPath)) {
                    BufferedImage img = ImageIO.read(new File(imgPath));
                    imgMap.put(imgPath, img);
                    //System.out.println("Read " + imgPath);
                }
            } catch (IOException e) {
                System.err.println("Read " + imgPath + " \n" + e);
            }
        }
        //change posBelong String(index) to image path
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                int index = Integer.parseInt(posBelong[i][j]);
                posBelong[i][j] = imgPaths[index];
            }
        }
        //initial imgPanels use image path posBelong
        initialImagePanels(imgMap, rowSize, colSize);
    }

    /**
     * initial the image panel, each panel with mouse clicked listener
     *
     * @param imgMap : fileName to Image map
     * @param width  : Panel default width
     * @param height : Panel default height
     */
    private void initialImagePanels(HashMap<String, BufferedImage> imgMap, int width, int height) {
        int rows = posBelong.length;
        int cols = posBelong[0].length;
        imgPanels = new PicturePanel[rows][cols];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < cols; j++) {
                imgPanels[i][j] = new PicturePanel(imgMap.get(posBelong[i][j]), width, height, i, j);
                imgPanels[i][j].addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        PicturePanel jp = (PicturePanel) (e.getSource());
                        Coordinate pos = jp.getCoordinate();
                        processEvent(new ActionEvent(jp, ActionEvent.ACTION_PERFORMED, pos.toString()));
                    }
                });
            }
        }
    }

    public int getRemainNumber() {
        return remainNumber;
    }

    public void setRemainNumber(int rNumber) {
        this.remainNumber = rNumber;
    }

    /**
     * set the position value and the corresponding panel non visible
     */
    public void setBlock(Coordinate pos, int value) {
        block[pos.getX() + 1][pos.getY() + 1] = value;
        imgPanels[pos.getX()][pos.getY()].setVisible(false);
        remainNumber -= 1;
    }

    public PicturePanel[][] getPicturePanels() {
        return imgPanels;
    }

    public synchronized void addActionListener(ActionListener l) {
        if (actionListenerList == null) {
            actionListenerList = new ArrayList<>();
        }
        actionListenerList.add(l);
    }

    public synchronized void removeActionListener(ActionListener l) {
        if (actionListenerList != null) {
            actionListenerList.remove(l);
        }
    }

    @SuppressWarnings("unchecked")
    private void processEvent(ActionEvent e) {
        ArrayList<ActionListener> list;
        synchronized (this) {
            if (actionListenerList == null) return;
            list = (ArrayList<ActionListener>) actionListenerList.clone();
        }
        for (ActionListener actionListener : list) {
            actionListener.actionPerformed(e);
        }
    }

    public String[][] getPosBelong() {
        return posBelong;
    }

}
